/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package controllers;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.*;
import views.GamePanel;

import models.*;

/**
 *
 * @author dennis
 */
public class Game {

    private boolean childMode = false;

    public GamePanel gamePanel;
    
    private Level level;
    private Menu menu;
    private Tile[][] matrix;
    private AvatarMovable avatar;
    private int avatarPosX, avatarPosY, countFinishTiles;
    private Sokoban sokoban;
    
    //private ArrayList<Tile[][]> history = new ArrayList<Tile[][]>();
    private Map<Integer,Tile[][]> history = new HashMap<Integer,Tile[][]>();
    /**
     * Creates a new GamePanel is there's no level loaded. If there's a level, sets the gamelevel.
     * @param sokoban Object of Sokoban
     */
    public Game(Sokoban sokoban){
        
        this.sokoban = sokoban;
        
        if(sokoban.level.getTileMatrix().length > 0){
             System.out.println("Creating GamePanel with new dimensions");
            this.setGameLevel(sokoban.level, sokoban.menu);
        } else {
            System.out.println("Creating GamePanel width dimensions 700, 700");
            this.gamePanel = new GamePanel(700, 700);
        }
        
    }
    
    /**
     * A user can choose a level in the menu, which creates a new game. 
     * This method sets the level, resets counters and creates a new GamePanel with the level matrix.
     * This also adds a keylistener, so we can move the avatar based on which arrowkey the user pressed.
     * @param level object of Level
     * @param menu object of Menu
     */
    public void setGameLevel(final Level level, final Menu menu){
        
        this.level = level; 
        this.menu = menu;
        
        this.avatar = this.level.getAvatar();
        this.countFinishTiles = this.level.getCountFinishTiles();
             
        this.matrix = this.level.getTileMatrix();
        
        this.menu.resetSteps();
        
        this.gamePanel = new GamePanel(this.matrix, this.level.getWidth(), this.level.getHeight());
        
        if(this.menu.getSelectedChildMode() == "On"){
            this.setChildMode(true);
        } else {
            this.setChildMode(false);
        }
        
        System.out.println("setGameLevel - new width: "+this.level.getWidth());
        System.out.println("setGameLevel - new height: "+this.level.getHeight());
        
        this.gamePanel.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e) {

            }

            @Override
            public void keyPressed(KeyEvent e) {
                
            }

            @Override
            public void keyReleased(KeyEvent e) {
                
                avatarPosX = avatar.getPosX();
                avatarPosY = avatar.getPosY();
                
                System.out.println("Current position: "+ avatarPosX + "," + avatarPosY);

                int newPosX = 0;
                int newPosY = 0;
                int incrementX = 0;
                int incrementY = 0;

                switch(e.getKeyCode()){
                    case 37: //left

                        incrementX = -1;
                        incrementY = 0;

                        break;
                    case 38: //up

                        incrementX = 0;
                        incrementY = -1;

                        break;
                    case 39: //right

                        incrementX = 1;
                        incrementY = 0;

                        break;
                    case 40: //down

                        incrementX = 0;
                        incrementY = 1;

                        break;
                }

                newPosX = (avatarPosX+incrementX);
                newPosY = (avatarPosY+incrementY);
                
                copyMatrix();
                
                if(move(avatar, newPosX, newPosY, incrementX, incrementY, 0)){
                    
                    menu.incrementStep();
                    gamePanel.setMatrix(matrix);
                    
                    if(level.isFinished()){
                        gamePanel.setFinished();
                    }
                    
                    int finished = level.getCountFinished();
                    int total    = level.getCountFinishTiles();
                    int progress = calculateProgress(finished, total);
                    
                    menu.setProgress(progress);
                    
                    gamePanel.repaint();
                    
                }
                
            }
            
        });
        
    }
    
    private void copyMatrix(){
       
        Tile[][] matrixCopy = new Tile[level.getRangeX()][level.getRangeY()];

        if(this.matrix.length > 0){
            for(int i = 0; i < this.matrix.length; i++){
                
                for(int j = 0; j < this.matrix[i].length; j++){
                    if(matrix[i][j] instanceof WallTile){
                        matrixCopy[i][j] = new WallTile( matrix[i][j] );
                    } else if(matrix[i][j] instanceof FinishTile){
                        matrixCopy[i][j] = new FinishTile( matrix[i][j] );
                    } else if(matrix[i][j] instanceof RoadTile){
                        matrixCopy[i][j] = new RoadTile( matrix[i][j] );
                    }
                }
                
            }
        }
        
        history.put(menu.getStep(), matrixCopy);
        
    }
    
    public void undoMove(){
        
        if(history.size() > 0){
            
            int lastStep = (int) (menu.getStep()-1);
                lastStep = ((lastStep < 0) ? 0 : lastStep);   

            System.out.println("Game - undoMove - currentStep: "+ menu.getStep());
            System.out.println("Game - undoMove - lastStep: "+ lastStep);

            menu.decrementStep(); 

            Tile[][] lastMatrix = history.get(lastStep);
            history.remove(lastStep);
            
            matrix = lastMatrix;
            
            level.setTileMatrix(matrix);
            this.avatar = level.getAvatar();
            
            int finished = level.getCountFinished();
            int total    = level.getCountFinishTiles();
            int progress = calculateProgress(finished, total);
            
            menu.setProgress(progress);
             
            gamePanel.setMatrix(matrix);
            gamePanel.repaint();
        
        }
        gamePanel.requestFocusInWindow();
    }
    
    /**
     * Sets the childmode to true or false
     * @param childMode this is true of false, and enables or disables childmode
     */
    public void setChildMode(boolean childMode){
        this.childMode = childMode;
    }
    
    /**
     * For example, we have 4 targettiles, and 3 boxes are on a targetfile. The process is 75%.
     * In this method we calculate the process.
     * @param finished amount of boxes that are on a finishtile
     * @param total total boxes
     * @return the process of the user in the current level.
     */
    public int calculateProgress(int finished, int total){ 
        return finished * 100 / total;
    }
    
    /**
     * This method is called when a user pressed an arrow key. This will update the level matrix with the new avatar position.
     * If the targetlocation contains a box, the box is moved (but only if it's possible to move the box).
     * @param movable this can be the avatar or a box
     * @param newPosX this is used to know the position of the targettile
     * @param newPosY this is used to know the position of the targettile
     * @param incrementX this is used to calculate the new position
     * @param incrementY this is used to calculate the new position
     * @param deep this method can call itself recursively, deep gets incremented every time and enables us to move multiple boxes if childmode is enabled
     * @return true of false (true means the move is allowed)
     */
    public boolean move(Movable movable, int newPosX, int newPosY, int incrementX, int incrementY, int deep){

        Tile targetTile = matrix[newPosX][newPosY];

        if(!targetTile.isAccessible()){
            return false;
        }
        
        if(deep > 2 && !this.childMode){
            return false;
        }

        if(targetTile.hasMovable()){

            Movable newMovable = targetTile.getMovable();
            newPosX = (movable.getPosX()+incrementX);
            newPosY = (movable.getPosY()+incrementY);

            if(!move(newMovable, newPosX, newPosY, incrementX, incrementY, ++deep)){
                return false;
            }

        }
        
        //sokoban.saveMatrix(matrix);
        
        matrix[movable.getPosX()][movable.getPosY()].removeMovable();
        matrix[newPosX][newPosY].setMovable(movable);

        movable.setPosition(newPosX, newPosY);

        return true;
        
    }

    /**
     * 
     * @return the gamepanel
     */
    public GamePanel getGamePanel(){
        return gamePanel;
    }
    
    
    
    
    
}
